/**
 * @flow
 */

import assert from 'assert';
import BN from 'bn.js';
import {Buffer} from 'buffer';
import * as BufferLayout from 'buffer-layout';
import type {Connection, TransactionSignature} from '@solana/web3.js';
import {
  Account,
  PublicKey,
  SystemProgram,
  Transaction,
  TransactionInstruction,
} from '@solana/web3.js';

import * as Layout from './layout';
import {sendAndConfirmTransaction} from './util/send-and-confirm-transaction';
import {loadAccount} from './util/account';

export const TOKEN_SWAP_PROGRAM_ID: PublicKey = new PublicKey(
  'HTpjeN1FocdopyJxwFJzBsQ5edeDVXkNB3ssePWmhfyj',
);

/**
 * Some amount of tokens
 */
export class Numberu64 extends BN {
  /**
   * Convert to Buffer representation
   */
  toBuffer(): typeof Buffer {
    const a = super.toArray().reverse();
    const b = Buffer.from(a);
    if (b.length === 8) {
      return b;
    }
    assert(b.length < 8, 'Numberu64 too large');

    const zeroPad = Buffer.alloc(8);
    b.copy(zeroPad);
    return zeroPad;
  }

  /**
   * Construct a Numberu64 from Buffer representation
   */
  static fromBuffer(buffer: typeof Buffer): Numberu64 {
    assert(buffer.length === 8, `Invalid buffer length: ${buffer.length}`);
    return new Numberu64(
      [...buffer]
        .reverse()
        .map(i => `00${i.toString(16)}`.slice(-2))
        .join(''),
      16,
    );
  }
}

/**
 * @private
 */
export const TokenSwapLayout: typeof BufferLayout.Structure = BufferLayout.struct(
  [
    BufferLayout.u8('version'),
    BufferLayout.u8('isInitialized'),
    BufferLayout.u8('nonce'),
    Layout.publicKey('tokenProgramId'),
    Layout.publicKey('tokenAccountA'),
    Layout.publicKey('tokenAccountB'),
    Layout.publicKey('tokenPool'),
    Layout.publicKey('mintA'),
    Layout.publicKey('mintB'),
    Layout.publicKey('feeAccount'),
    Layout.uint64('tradeFeeNumerator'),
    Layout.uint64('tradeFeeDenominator'),
    Layout.uint64('ownerTradeFeeNumerator'),
    Layout.uint64('ownerTradeFeeDenominator'),
    Layout.uint64('ownerWithdrawFeeNumerator'),
    Layout.uint64('ownerWithdrawFeeDenominator'),
    Layout.uint64('hostFeeNumerator'),
    Layout.uint64('hostFeeDenominator'),
    BufferLayout.u8('curveType'),
    BufferLayout.blob(32, 'curveParameters'),
  ],
);

/**
 * // farm 结构体
 * @private
 */
export const TokenFarmLayout: typeof BufferLayout.Structure = BufferLayout.struct(
  [
    BufferLayout.u8('isInitialized'), // 1-2
    BufferLayout.u8('nonce'), // 1
    Layout.publicKey('tokenProgramId'),
    Layout.publicKey('farmMint'), // farm 合约地址
    Layout.publicKey('poolFarmAddress'), // 矿池 farm地址
    Layout.publicKey('lpMint'), // lp 合约地址
    Layout.publicKey('poolLpAddress'), // 矿池 lp地址
    Layout.publicKey('poolFeeLpAddress'), // 矿池 lp手续费地址
    Layout.uint64('tradeFeeNumerator'),
    Layout.uint64('tradeFeeDenominator'),
    Layout.uint64('ownerTradeFeeNumerator'),
    Layout.uint64('ownerTradeFeeDenominator'),
    Layout.uint64('ownerWithdrawFeeNumerator'),
    Layout.uint64('ownerWithdrawFeeDenominator'),
    Layout.uint64('hostFeeNumerator'),
    Layout.uint64('hostFeeDenominator'),
    Layout.uint64('lastUpdate'), // 最后一次slot
    Layout.uint64('rewardPerSecond'), // 每slot奖励
    Layout.uint64('rewardPerShare'),// 每个份额奖励
    Layout.uint64('totalSupply'), // 总质押量
    Layout.publicKey('owner'), // owner
  ],
);

/**
 * // farm user 结构体
 * @private
 */
export const TokenFarmUserLayout: typeof BufferLayout.Structure = BufferLayout.struct(
  [
      BufferLayout.u8('isInitialized'), // 1-2
      Layout.uint64('amount'), // 质押金额
      Layout.uint64('rewardDebt'),// 理论已领取奖励
  ],
);

export const CurveType = Object.freeze({
  ConstantProduct: 0, // Constant product curve, Uniswap-style
  ConstantPrice: 1, // Constant price curve, always X amount of A token for 1 B token, where X is defined at init
  Offset: 3, // Offset curve, like Uniswap, but with an additional offset on the token B side
});

/**
 * A program to exchange tokens against a pool of liquidity
 */
export class TokenSwap {
  /**
   * @private
   */
  connection: Connection;

  /**
   * Program Identifier for the Swap program
   */
  swapProgramId: PublicKey;

  /**
   * Program Identifier for the Token program
   */
  tokenProgramId: PublicKey;

  /**
   * The public key identifying this swap program
   */
  tokenSwap: PublicKey;

  /**
   * The public key for the liquidity pool token mint
   */
  poolToken: PublicKey;

  /**
   * The public key for the fee account receiving trade and/or withdrawal fees
   */
  feeAccount: PublicKey;

  /**
   * Authority
   */
  authority: PublicKey;

  /**
   * The public key for the first token account of the trading pair
   */
  tokenAccountA: PublicKey;

  /**
   * The public key for the second token account of the trading pair
   */
  tokenAccountB: PublicKey;

  /**
   * The public key for the mint of the first token account of the trading pair
   */
  mintA: PublicKey;

  /**
   * The public key for the mint of the second token account of the trading pair
   */
  mintB: PublicKey;

  /**
   * Trading fee numerator
   */
  tradeFeeNumerator: Numberu64;

  /**
   * Trading fee denominator
   */
  tradeFeeDenominator: Numberu64;

  /**
   * Owner trading fee numerator
   */
  ownerTradeFeeNumerator: Numberu64;

  /**
   * Owner trading fee denominator
   */
  ownerTradeFeeDenominator: Numberu64;

  /**
   * Owner withdraw fee numerator
   */
  ownerWithdrawFeeNumerator: Numberu64;

  /**
   * Owner withdraw fee denominator
   */
  ownerWithdrawFeeDenominator: Numberu64;

  /**
   * Host trading fee numerator
   */
  hostFeeNumerator: Numberu64;

  /**
   * Host trading fee denominator
   */
  hostFeeDenominator: Numberu64;

  /**
   * CurveType, current options are:
   */
  curveType: number;

  /**
   * Fee payer
   */
  payer: Account;

  /**
   * Create a Token object attached to the specific token
   *
   * @param connection The connection to use
   * @param tokenSwap The token swap account
   * @param swapProgramId The program ID of the token-swap program
   * @param tokenProgramId The program ID of the token program
   * @param poolToken The pool token
   * @param authority The authority over the swap and accounts
   * @param tokenAccountA: The token swap's Token A account
   * @param tokenAccountB: The token swap's Token B account
   * @param payer Pays for the transaction
   */
  constructor(
    connection: Connection,
    tokenSwap: PublicKey,
    swapProgramId: PublicKey,
    tokenProgramId: PublicKey,
    poolToken: PublicKey,
    feeAccount: PublicKey,
    authority: PublicKey,
    tokenAccountA: PublicKey,
    tokenAccountB: PublicKey,
    mintA: PublicKey,
    mintB: PublicKey,
    tradeFeeNumerator: Numberu64,
    tradeFeeDenominator: Numberu64,
    ownerTradeFeeNumerator: Numberu64,
    ownerTradeFeeDenominator: Numberu64,
    ownerWithdrawFeeNumerator: Numberu64,
    ownerWithdrawFeeDenominator: Numberu64,
    hostFeeNumerator: Numberu64,
    hostFeeDenominator: Numberu64,
    curveType: number,
    payer: Account,
  ) {
    Object.assign(this, {
      connection,
      tokenSwap,
      swapProgramId,
      tokenProgramId,
      poolToken,
      feeAccount,
      authority,
      tokenAccountA,
      tokenAccountB,
      mintA,
      mintB,
      tradeFeeNumerator,
      tradeFeeDenominator,
      ownerTradeFeeNumerator,
      ownerTradeFeeDenominator,
      ownerWithdrawFeeNumerator,
      ownerWithdrawFeeDenominator,
      hostFeeNumerator,
      hostFeeDenominator,
      curveType,
      payer,
    });
  }

  /**
   * Get the minimum balance for the token swap account to be rent exempt
   *
   * @return Number of lamports required
   */
  static async getMinBalanceRentForExemptTokenSwap(
    connection: Connection,
  ): Promise<number> {
    return await connection.getMinimumBalanceForRentExemption(
      TokenSwapLayout.span,
    );
  }

  static createInitSwapInstruction(
    tokenSwapAccount: Account,
    authority: PublicKey,
    tokenAccountA: PublicKey,
    tokenAccountB: PublicKey,
    tokenPool: PublicKey,
    feeAccount: PublicKey,
    tokenAccountPool: PublicKey,
    tokenProgramId: PublicKey,
    swapProgramId: PublicKey,
    nonce: number,
    tradeFeeNumerator: number,
    tradeFeeDenominator: number,
    ownerTradeFeeNumerator: number,
    ownerTradeFeeDenominator: number,
    ownerWithdrawFeeNumerator: number,
    ownerWithdrawFeeDenominator: number,
    hostFeeNumerator: number,
    hostFeeDenominator: number,
    curveType: number,
  ): TransactionInstruction {
    const keys = [
      {pubkey: tokenSwapAccount.publicKey, isSigner: false, isWritable: true},
      {pubkey: authority, isSigner: false, isWritable: false},
      {pubkey: tokenAccountA, isSigner: false, isWritable: false},
      {pubkey: tokenAccountB, isSigner: false, isWritable: false},
      {pubkey: tokenPool, isSigner: false, isWritable: true},
      {pubkey: feeAccount, isSigner: false, isWritable: false},
      {pubkey: tokenAccountPool, isSigner: false, isWritable: true},
      {pubkey: tokenProgramId, isSigner: false, isWritable: false},
    ];
    const commandDataLayout = BufferLayout.struct([
      BufferLayout.u8('instruction'),
      BufferLayout.u8('nonce'),
      BufferLayout.nu64('tradeFeeNumerator'),
      BufferLayout.nu64('tradeFeeDenominator'),
      BufferLayout.nu64('ownerTradeFeeNumerator'),
      BufferLayout.nu64('ownerTradeFeeDenominator'),
      BufferLayout.nu64('ownerWithdrawFeeNumerator'),
      BufferLayout.nu64('ownerWithdrawFeeDenominator'),
      BufferLayout.nu64('hostFeeNumerator'),
      BufferLayout.nu64('hostFeeDenominator'),
      BufferLayout.u8('curveType'),
      BufferLayout.blob(32, 'curveParameters'),
    ]);
    let data = Buffer.alloc(1024);
    {
      const encodeLength = commandDataLayout.encode(
        {
          instruction: 0, // InitializeSwap instruction
          nonce,
          tradeFeeNumerator,
          tradeFeeDenominator,
          ownerTradeFeeNumerator,
          ownerTradeFeeDenominator,
          ownerWithdrawFeeNumerator,
          ownerWithdrawFeeDenominator,
          hostFeeNumerator,
          hostFeeDenominator,
          curveType,
        },
        data,
      );
      data = data.slice(0, encodeLength);
    }
    return new TransactionInstruction({
      keys,
      programId: swapProgramId,
      data,
    });
  }

  static createInitFarmInstruction(
      tokenProgramId: PublicKey, // 通用token 转账 的地址
      farm_info: Account, //
      authority: PublicKey, //
      farm_mint: PublicKey, //
      pool_farm_address: PublicKey, //
      lp_mint: PublicKey, //
      pool_lp_address: PublicKey, //
      pool_fee_lp_address: PublicKey, //
      swapProgramId: PublicKey, // swap-farm 程序地址
      nonce: number,
      tradeFeeNumerator: number,
      tradeFeeDenominator: number,
      ownerTradeFeeNumerator: number,
      ownerTradeFeeDenominator: number,
      ownerWithdrawFeeNumerator: number,
      ownerWithdrawFeeDenominator: number,
      hostFeeNumerator: number,
      hostFeeDenominator: number,
      reward_per_second: number,
  ): TransactionInstruction {
      // 时间合约地址 记录区块的时间信息 slot
      let clock = new PublicKey('SysvarC1ock11111111111111111111111111111111');
      const keys = [
          {pubkey: tokenProgramId, isSigner: false, isWritable: false},// 通用token地址
          {pubkey: farm_info.publicKey, isSigner: false, isWritable: true}, // farm 矿池地址
          {pubkey: authority, isSigner: false, isWritable: false}, // 矿池auth
          {pubkey: farm_mint, isSigner: false, isWritable: false},// farm 合约地址
          {pubkey: pool_farm_address, isSigner: false, isWritable: false},// 矿池对应的farm接收地址
          {pubkey: lp_mint, isSigner: false, isWritable: false},// lp合约地址
          {pubkey: pool_lp_address, isSigner: false, isWritable: false},// 矿池对应的lp接收地址
          {pubkey: pool_fee_lp_address, isSigner: false, isWritable: false},// 手续费地址
          {pubkey: clock, isSigner: false, isWritable: false},
      ];
      const commandDataLayout = BufferLayout.struct([
          BufferLayout.u8('instruction'),
          BufferLayout.u8('nonce'),
          BufferLayout.nu64('tradeFeeNumerator'),
          BufferLayout.nu64('tradeFeeDenominator'),
          BufferLayout.nu64('ownerTradeFeeNumerator'),
          BufferLayout.nu64('ownerTradeFeeDenominator'),
          BufferLayout.nu64('ownerWithdrawFeeNumerator'),
          BufferLayout.nu64('ownerWithdrawFeeDenominator'),
          BufferLayout.nu64('hostFeeNumerator'),
          BufferLayout.nu64('hostFeeDenominator'),
          BufferLayout.nu64('reward_per_second'),
      ]);
      let data = Buffer.alloc(1024);
      {
          const encodeLength = commandDataLayout.encode(
              {
                  instruction: 6, // InitializeFarm instruction
                  nonce,
                  tradeFeeNumerator,
                  tradeFeeDenominator,
                  ownerTradeFeeNumerator,
                  ownerTradeFeeDenominator,
                  ownerWithdrawFeeNumerator,
                  ownerWithdrawFeeDenominator,
                  hostFeeNumerator,
                  hostFeeDenominator,
                  reward_per_second,
              },
              data,
          );
          data = data.slice(0, encodeLength);
      }
      return new TransactionInstruction({
          keys,
          programId: swapProgramId,
          data,
      });
  }

  static depositFarmInstruction(
      farm_info: PublicKey,
      authority_info:PublicKey,// 矿池的owner账户
      user_lp_address:PublicKey,// 用户接收farm奖励的地址
      user_farm_address:PublicKey,// 用户接收farm奖励的地址
      user_info:PublicKey,// 矿池接收lp的地址
      pool_lp_address:PublicKey,// 矿池接收lp的地址
      user_transfer_authority_info:PublicKey,// 委托方地址
      tokenProgramId:PublicKey,
      swapProgramId:PublicKey,
      pool_farm_address:PublicKey,
      pool_fee_lp_account:PublicKey,
      amount: number,
  ): TransactionInstruction {
      // 时间合约地址
      let clock = new PublicKey('SysvarC1ock11111111111111111111111111111111');
      const keys = [
          {pubkey: farm_info, isSigner: false, isWritable: true}, // 矿池信息
          {pubkey: authority_info, isSigner: false, isWritable: true},// 矿池代币owner
          {pubkey: user_info, isSigner: false, isWritable: true},
          {pubkey: user_farm_address, isSigner: false, isWritable: true},
          {pubkey: user_lp_address, isSigner: false, isWritable: true},
          {pubkey: pool_lp_address, isSigner: false, isWritable: true},
          {pubkey: user_transfer_authority_info, isSigner: true, isWritable: true},
          {pubkey: tokenProgramId, isSigner: false, isWritable: false},
          {pubkey: pool_farm_address, isSigner: false, isWritable: true},
          {pubkey: clock, isSigner: false, isWritable: false},
          {pubkey: pool_fee_lp_account, isSigner: false, isWritable: true},
      ];
      const commandDataLayout = BufferLayout.struct([
          BufferLayout.u8('instruction'),
          BufferLayout.nu64('amount'),
      ]);
      let data = Buffer.alloc(1024);
      {
          const encodeLength = commandDataLayout.encode(
              {
                  instruction: 7, // Deposit instruction
                  amount,
              },
              data,
          );
          data = data.slice(0, encodeLength);
      }
      return new TransactionInstruction({
          keys,
          programId: swapProgramId,
          data,
      });
  }

  static withdrawFarmInstruction(
      farm_info: PublicKey,
      authority_info:PublicKey,// 矿池的owner账户
      user_lp_address:PublicKey,// 用户接收farm奖励的地址
      user_farm_address:PublicKey,// 用户接收farm奖励的地址
      user_info:PublicKey,// 矿池接收lp的地址
      pool_lp_address:PublicKey,// 矿池接收lp的地址
      tokenProgramId:PublicKey,
      swapProgramId:PublicKey,
      pool_farm_address:PublicKey,
      pool_fee_lp_account:PublicKey,
      amount: number,
  ): TransactionInstruction {
      // 时间合约地址
      let clock = new PublicKey('SysvarC1ock11111111111111111111111111111111');
      const keys = [
          {pubkey: farm_info, isSigner: false, isWritable: true}, // 矿池信息
          {pubkey: authority_info, isSigner: false, isWritable: true},// 矿池代币owner
          {pubkey: user_info, isSigner: false, isWritable: true},
          {pubkey: user_farm_address, isSigner: false, isWritable: true},
          {pubkey: user_lp_address, isSigner: false, isWritable: true},
          {pubkey: pool_lp_address, isSigner: false, isWritable: true},
          {pubkey: tokenProgramId, isSigner: false, isWritable: false},
          {pubkey: pool_farm_address, isSigner: false, isWritable: true},
          {pubkey: clock, isSigner: false, isWritable: false},
          {pubkey: pool_fee_lp_account, isSigner: false, isWritable: true},
      ];
      const commandDataLayout = BufferLayout.struct([
          BufferLayout.u8('instruction'),
          BufferLayout.nu64('amount'),
      ]);
      let data = Buffer.alloc(1024);
      {
          const encodeLength = commandDataLayout.encode(
              {
                  instruction: 8, // Withdraw instruction
                  amount,
              },
              data,
          );
          data = data.slice(0, encodeLength);
      }
      return new TransactionInstruction({
          keys,
          programId: swapProgramId,
          data,
      });
  }

  static async loadTokenSwap(
    connection: Connection,
    address: PublicKey,
    programId: PublicKey,
    payer: Account,
  ): Promise<TokenSwap> {
    const data = await loadAccount(connection, address, programId);
    const tokenSwapData = TokenSwapLayout.decode(data);
    if (!tokenSwapData.isInitialized) {
      throw new Error(`Invalid token swap state`);
    }

    const [authority] = await PublicKey.findProgramAddress(
      [address.toBuffer()],
      programId,
    );

    const poolToken = new PublicKey(tokenSwapData.tokenPool);
    const feeAccount = new PublicKey(tokenSwapData.feeAccount);
    const tokenAccountA = new PublicKey(tokenSwapData.tokenAccountA);
    const tokenAccountB = new PublicKey(tokenSwapData.tokenAccountB);
    const mintA = new PublicKey(tokenSwapData.mintA);
    const mintB = new PublicKey(tokenSwapData.mintB);
    const tokenProgramId = new PublicKey(tokenSwapData.tokenProgramId);

    const tradeFeeNumerator = Numberu64.fromBuffer(
      tokenSwapData.tradeFeeNumerator,
    );
    const tradeFeeDenominator = Numberu64.fromBuffer(
      tokenSwapData.tradeFeeDenominator,
    );
    const ownerTradeFeeNumerator = Numberu64.fromBuffer(
      tokenSwapData.ownerTradeFeeNumerator,
    );
    const ownerTradeFeeDenominator = Numberu64.fromBuffer(
      tokenSwapData.ownerTradeFeeDenominator,
    );
    const ownerWithdrawFeeNumerator = Numberu64.fromBuffer(
      tokenSwapData.ownerWithdrawFeeNumerator,
    );
    const ownerWithdrawFeeDenominator = Numberu64.fromBuffer(
      tokenSwapData.ownerWithdrawFeeDenominator,
    );
    const hostFeeNumerator = Numberu64.fromBuffer(
      tokenSwapData.hostFeeNumerator,
    );
    const hostFeeDenominator = Numberu64.fromBuffer(
      tokenSwapData.hostFeeDenominator,
    );
    const curveType = tokenSwapData.curveType;

    return new TokenSwap(
      connection,
      address,
      programId,
      tokenProgramId,
      poolToken,
      feeAccount,
      authority,
      tokenAccountA,
      tokenAccountB,
      mintA,
      mintB,
      tradeFeeNumerator,
      tradeFeeDenominator,
      ownerTradeFeeNumerator,
      ownerTradeFeeDenominator,
      ownerWithdrawFeeNumerator,
      ownerWithdrawFeeDenominator,
      hostFeeNumerator,
      hostFeeDenominator,
      curveType,
      payer,
    );
  }

  static async loadTokenFarm(
    connection: Connection,
    address: PublicKey,
    programId: PublicKey,
  ): Promise<Object> {
      const data = await loadAccount(connection, address, programId);
      const tokenSwapData = TokenFarmLayout.decode(data);
      if (!tokenSwapData.isInitialized) {
          throw new Error(`未初始化`);
      }

      const [authority] = await PublicKey.findProgramAddress(
          [address.toBuffer()],
          programId,
      );
      let poolInfo = {};
      poolInfo['authority'] = authority;
      poolInfo['tradeFeeNumerator'] = Numberu64.fromBuffer(
          tokenSwapData.tradeFeeNumerator,
      );
      poolInfo['ownerTradeFeeNumerator'] = Numberu64.fromBuffer(
          tokenSwapData.ownerTradeFeeNumerator,
      );
      poolInfo['tradeFeeDenominator'] = Numberu64.fromBuffer(
          tokenSwapData.tradeFeeDenominator,
      );
      poolInfo['ownerTradeFeeDenominator'] = Numberu64.fromBuffer(
          tokenSwapData.ownerTradeFeeDenominator,
      );
      poolInfo['ownerWithdrawFeeNumerator'] = Numberu64.fromBuffer(
          tokenSwapData.ownerWithdrawFeeNumerator,
      );
      poolInfo['ownerWithdrawFeeDenominator'] = Numberu64.fromBuffer(
          tokenSwapData.ownerWithdrawFeeDenominator,
      );
      poolInfo['hostFeeNumerator'] = Numberu64.fromBuffer(
          tokenSwapData.hostFeeNumerator,
      );
      poolInfo['hostFeeDenominator'] = Numberu64.fromBuffer(
          tokenSwapData.hostFeeDenominator,
      );
      poolInfo['token_program_id'] = new PublicKey(tokenSwapData.tokenProgramId);
      poolInfo['farm_mint'] = new PublicKey(tokenSwapData.farmMint);
      poolInfo['pool_farm_address'] = new PublicKey(tokenSwapData.poolFarmAddress);
      poolInfo['lp_mint'] = new PublicKey(tokenSwapData.lpMint);
      poolInfo['pool_lp_address'] = new PublicKey(tokenSwapData.poolLpAddress);
      poolInfo['pool_fee_lp_address'] = new PublicKey(tokenSwapData.poolFeeLpAddress);
      poolInfo['last_update'] = new PublicKey(tokenSwapData.lastUpdate);
      poolInfo['reward_per_second'] = new PublicKey(tokenSwapData.rewardPerSecond);
      poolInfo['reward_per_share'] = new PublicKey(tokenSwapData.rewardPerShare);
      poolInfo['total_supply'] = new PublicKey(tokenSwapData.totalSupply);
      return poolInfo;
  }

    static async loadTokenFarmUser(
        connection: Connection,
        address: PublicKey,
        programId: PublicKey,
    ): Promise<Object> {
        const data = await loadAccount(connection, address, programId);
        const userData = TokenFarmUserLayout.decode(data);
        if (!userData.isInitialized) {
            throw new Error(`未初始化`);
        }
        console.log(userData.amount.toString())
        let userInfo = {};
        userInfo['amount'] = Numberu64.fromBuffer(
            userData.amount,
        );
        userInfo['reward_debt'] = Numberu64.fromBuffer(
            userData.rewardDebt,
        );
        return userInfo;
    }

  /**
   * Create a new Token Swap
   *
   * @param connection The connection to use
   * @param payer Pays for the transaction
   * @param tokenSwapAccount The token swap account
   * @param authority The authority over the swap and accounts
   * @param nonce The nonce used to generate the authority
   * @param tokenAccountA: The token swap's Token A account
   * @param tokenAccountB: The token swap's Token B account
   * @param poolToken The pool token
   * @param tokenAccountPool The token swap's pool token account
   * @param tokenProgramId The program ID of the token program
   * @param swapProgramId The program ID of the token-swap program
   * @param feeNumerator Numerator of the fee ratio
   * @param feeDenominator Denominator of the fee ratio
   * @return Token object for the newly minted token, Public key of the account holding the total supply of new tokens
   */
  static async createTokenSwap(
    connection: Connection,
    payer: Account,
    tokenSwapAccount: Account,
    authority: PublicKey,
    tokenAccountA: PublicKey,
    tokenAccountB: PublicKey,
    poolToken: PublicKey,
    mintA: PublicKey,
    mintB: PublicKey,
    feeAccount: PublicKey,
    tokenAccountPool: PublicKey,
    swapProgramId: PublicKey,
    tokenProgramId: PublicKey,
    nonce: number,
    tradeFeeNumerator: number,
    tradeFeeDenominator: number,
    ownerTradeFeeNumerator: number,
    ownerTradeFeeDenominator: number,
    ownerWithdrawFeeNumerator: number,
    ownerWithdrawFeeDenominator: number,
    hostFeeNumerator: number,
    hostFeeDenominator: number,
    curveType: number,
  ): Promise<TokenSwap> {
    let transaction;
    const tokenSwap = new TokenSwap(
      connection,
      tokenSwapAccount.publicKey,
      swapProgramId,
      tokenProgramId,
      poolToken,
      feeAccount,
      authority,
      tokenAccountA,
      tokenAccountB,
      mintA,
      mintB,
      new Numberu64(tradeFeeNumerator),
      new Numberu64(tradeFeeDenominator),
      new Numberu64(ownerTradeFeeNumerator),
      new Numberu64(ownerTradeFeeDenominator),
      new Numberu64(ownerWithdrawFeeNumerator),
      new Numberu64(ownerWithdrawFeeDenominator),
      new Numberu64(hostFeeNumerator),
      new Numberu64(hostFeeDenominator),
      curveType,
      payer,
    );

    // Allocate memory for the account
    const balanceNeeded = await TokenSwap.getMinBalanceRentForExemptTokenSwap(
      connection,
    );
    transaction = new Transaction();
    transaction.add(
      SystemProgram.createAccount({
        fromPubkey: payer.publicKey,
        newAccountPubkey: tokenSwapAccount.publicKey,
        lamports: balanceNeeded,
        space: TokenSwapLayout.span,
        programId: swapProgramId,
      }),
    );

    const instruction = TokenSwap.createInitSwapInstruction(
      tokenSwapAccount,
      authority,
      tokenAccountA,
      tokenAccountB,
      poolToken,
      feeAccount,
      tokenAccountPool,
      tokenProgramId,
      swapProgramId,
      nonce,
      tradeFeeNumerator,
      tradeFeeDenominator,
      ownerTradeFeeNumerator,
      ownerTradeFeeDenominator,
      ownerWithdrawFeeNumerator,
      ownerWithdrawFeeDenominator,
      hostFeeNumerator,
      hostFeeDenominator,
      curveType,
    );

    transaction.add(instruction);
    await sendAndConfirmTransaction(
      'createAccount and InitializeSwap',
      connection,
      transaction,
      payer,
      tokenSwapAccount,
    );

    return tokenSwap;
  }

  /**
   * Create a new Token Swap
   *
   * @param connection The connection to use
   * @param payer Pays for the transaction
   * @param tokenSwapAccount The token swap account
   * @param authority The authority over the swap and accounts
   * @param nonce The nonce used to generate the authority
   * @param tokenAccountA: The token swap's Token A account
   * @param tokenAccountB: The token swap's Token B account
   * @param poolToken The pool token
   * @param tokenAccountPool The token swap's pool token account
   * @param tokenProgramId The program ID of the token program
   * @param swapProgramId The program ID of the token-swap program
   * @param feeNumerator Numerator of the fee ratio
   * @param feeDenominator Denominator of the fee ratio
   * @return Token object for the newly minted token, Public key of the account holding the total supply of new tokens
   */
  static async createTokenFarm(
    connection: Connection,
    payer: Account,
    tokenProgramId: PublicKey,
    farm_info: Account,
    authority: PublicKey,
    farm_mint: PublicKey,
    pool_farm_address: PublicKey,
    lp_mint : PublicKey, //
    pool_lp_address : PublicKey, //
    pool_fee_lp_address : PublicKey, //
    swapProgramId: PublicKey,
    nonce: number,
    tradeFeeNumerator: number,
    tradeFeeDenominator: number,
    ownerTradeFeeNumerator: number,
    ownerTradeFeeDenominator: number,
    ownerWithdrawFeeNumerator: number,
    ownerWithdrawFeeDenominator: number,
    hostFeeNumerator: number,
    hostFeeDenominator: number,
    reward_per_second: number,
  ) {
    let transaction;
    // Allocate memory for the account
    const balanceNeeded = await TokenSwap.getMinBalanceRentForExemptTokenSwap(
      connection,
    );
    transaction = new Transaction();
    transaction.add(
      SystemProgram.createAccount({
        fromPubkey: payer.publicKey,
        newAccountPubkey: farm_info.publicKey,
        lamports: balanceNeeded,
        space: TokenSwapLayout.span,
        programId: swapProgramId,
      }),
    );
    const instruction = TokenSwap.createInitFarmInstruction(
        tokenProgramId, // 通用token 转账 的地址
        farm_info, //
        authority, //
        farm_mint, //
        pool_farm_address,
        lp_mint, //
        pool_lp_address, //
        pool_fee_lp_address, //
        swapProgramId,
        nonce,
        tradeFeeNumerator,
        tradeFeeDenominator,
        ownerTradeFeeNumerator,
        ownerTradeFeeDenominator,
        ownerWithdrawFeeNumerator,
        ownerWithdrawFeeDenominator,
        hostFeeNumerator,
        hostFeeDenominator,
        reward_per_second,
    );

    transaction.add(instruction);
    let result = await sendAndConfirmTransaction(
      'createAccount and InitializeFarm',
      connection,
      transaction,
      payer,
      farm_info,
    );
    console.log("result:",result)
  }
  static async depositFarm(
    connection: Connection,
    payer: Account,
    farm_info: PublicKey,
    authority_info:PublicKey,// 矿池的owner账户
    user_lp_address:PublicKey,// 用户lp的地址
    user_farm_address:PublicKey,// 用户接收farm奖励的地址
    user_info:Account,// 用户详情
    pool_lp_address:PublicKey,// 矿池接收lp的地址
    user_transfer_authority_info:Account,// 委托方地址
    tokenProgramId:PublicKey,
    swapProgramId:PublicKey,
    pool_farm_address:PublicKey,
    pool_fee_lp_account:PublicKey,
    amount: number,
  ) {
    let transaction = new Transaction();
      const balanceNeeded = await TokenSwap.getMinBalanceRentForExemptTokenSwap(
          connection,
      );
      transaction.add(
          SystemProgram.createAccount({
              fromPubkey: payer.publicKey,
              newAccountPubkey: user_info.publicKey,
              lamports: balanceNeeded,
              space: TokenSwapLayout.span,
              programId: swapProgramId,
          }),
      );
    const instruction = TokenSwap.depositFarmInstruction(
        farm_info,
        authority_info,// 矿池的owner账户
        user_lp_address,// 用户接收farm奖励的地址
        user_farm_address,// 用户接收farm奖励的地址
        user_info.publicKey,// 矿池接收lp的地址
        pool_lp_address,// 矿池接收lp的地址
        user_transfer_authority_info.publicKey,// 委托方地址
        tokenProgramId,
        swapProgramId,
        pool_farm_address,
        pool_fee_lp_account,
        amount,
    );
    transaction.add(instruction);
    let result = await sendAndConfirmTransaction(
        'deposit',
        connection,
        transaction,
        payer,
        user_transfer_authority_info,
        user_info,
    );
    console.log("result:",result)
  }
  static async withdrawFarm(
    connection: Connection,
    payer: Account,
    farm_info: PublicKey,
    authority_info:PublicKey,// 矿池的owner账户
    user_lp_address:PublicKey,// 用户lp的地址
    user_farm_address:PublicKey,// 用户接收farm奖励的地址
    user_info:Account,// 用户详情
    pool_lp_address:PublicKey,// 矿池接收lp的地址
    tokenProgramId:PublicKey,
    swapProgramId:PublicKey,
    pool_farm_address:PublicKey,
    pool_fee_lp_account:PublicKey,
    amount: number,
  ) {
    let transaction = new Transaction();
    const instruction = TokenSwap.withdrawFarmInstruction(
        farm_info,
        authority_info,// 矿池的owner账户
        user_lp_address,// 用户接收farm奖励的地址
        user_farm_address,// 用户接收farm奖励的地址
        user_info.publicKey,// 矿池接收lp的地址
        pool_lp_address,// 矿池接收lp的地址
        tokenProgramId,
        swapProgramId,
        pool_farm_address,
        pool_fee_lp_account,
        amount,
    );
    transaction.add(instruction);
    let result = await sendAndConfirmTransaction(
        'deposit',
        connection,
        transaction,
        payer,
    );
    console.log("result:",result)
  }
  static async harvestFarm(
      connection: Connection,
      payer: Account,
      farm_info: PublicKey,
      authority_info:PublicKey,// 矿池的owner账户
      user_lp_address:PublicKey,// 用户lp的地址
      user_farm_address:PublicKey,// 用户接收farm奖励的地址
      user_info:Account,// 用户详情
      pool_lp_address:PublicKey,// 矿池接收lp的地址
      user_transfer_authority_info:Account,// 委托方地址
      tokenProgramId:PublicKey,
      swapProgramId:PublicKey,
      pool_farm_address:PublicKey,
      pool_fee_lp_account:PublicKey,
      amount: number,
  ) {
    let transaction = new Transaction();
    const instruction = TokenSwap.depositFarmInstruction(
        farm_info,
        authority_info,// 矿池的owner账户
        user_lp_address,// 用户接收farm奖励的地址
        user_farm_address,// 用户接收farm奖励的地址
        user_info.publicKey,// 矿池接收lp的地址
        pool_lp_address,// 矿池接收lp的地址
        user_transfer_authority_info.publicKey,// 委托方地址
        tokenProgramId,
        swapProgramId,
        pool_farm_address,
        pool_fee_lp_account,
        amount,
    );
    transaction.add(instruction);
    let result = await sendAndConfirmTransaction(
        'harvest',
        connection,
        transaction,
        payer,
        user_transfer_authority_info,
    );
    console.log("result:",result)
  }

  /**
   * Swap token A for token B
   *
   * @param userSource User's source token account
   * @param poolSource Pool's source token account
   * @param poolDestination Pool's destination token account
   * @param userDestination User's destination token account
   * @param hostFeeAccount Host account to gather fees
   * @param userTransferAuthority Account delegated to transfer user's tokens
   * @param amountIn Amount to transfer from source account
   * @param minimumAmountOut Minimum amount of tokens the user will receive
   */
  async swap(
    userSource: PublicKey,
    poolSource: PublicKey,
    poolDestination: PublicKey,
    userDestination: PublicKey,
    hostFeeAccount: ?PublicKey,
    userTransferAuthority: Account,
    amountIn: number | Numberu64,
    minimumAmountOut: number | Numberu64,
  ): Promise<TransactionSignature> {
    return await sendAndConfirmTransaction(
      'swap',
      this.connection,
      new Transaction().add(
        TokenSwap.swapInstruction(
          this.tokenSwap,
          this.authority,
          userTransferAuthority.publicKey,
          userSource,
          poolSource,
          poolDestination,
          userDestination,
          this.poolToken,
          this.feeAccount,
          hostFeeAccount,
          this.swapProgramId,
          this.tokenProgramId,
          amountIn,
          minimumAmountOut,
        ),
      ),
      this.payer,
      userTransferAuthority,
    );
  }

  static swapInstruction(
    tokenSwap: PublicKey,
    authority: PublicKey,
    userTransferAuthority: PublicKey,
    userSource: PublicKey,
    poolSource: PublicKey,
    poolDestination: PublicKey,
    userDestination: PublicKey,
    poolMint: PublicKey,
    feeAccount: PublicKey,
    hostFeeAccount: ?PublicKey,
    swapProgramId: PublicKey,
    tokenProgramId: PublicKey,
    amountIn: number | Numberu64,
    minimumAmountOut: number | Numberu64,
  ): TransactionInstruction {
    const dataLayout = BufferLayout.struct([
      BufferLayout.u8('instruction'),
      Layout.uint64('amountIn'),
      Layout.uint64('minimumAmountOut'),
    ]);

    const data = Buffer.alloc(dataLayout.span);
    dataLayout.encode(
      {
        instruction: 1, // Swap instruction
        amountIn: new Numberu64(amountIn).toBuffer(),
        minimumAmountOut: new Numberu64(minimumAmountOut).toBuffer(),
      },
      data,
    );

    const keys = [
      {pubkey: tokenSwap, isSigner: false, isWritable: false},
      {pubkey: authority, isSigner: false, isWritable: false},
      {pubkey: userTransferAuthority, isSigner: true, isWritable: false},
      {pubkey: userSource, isSigner: false, isWritable: true},
      {pubkey: poolSource, isSigner: false, isWritable: true},
      {pubkey: poolDestination, isSigner: false, isWritable: true},
      {pubkey: userDestination, isSigner: false, isWritable: true},
      {pubkey: poolMint, isSigner: false, isWritable: true},
      {pubkey: feeAccount, isSigner: false, isWritable: true},
      {pubkey: tokenProgramId, isSigner: false, isWritable: false},
    ]; // 账户  pubkey地址 账户.pubkey
    if (hostFeeAccount != null) {
      keys.push({pubkey: hostFeeAccount, isSigner: false, isWritable: true});
    }
    return new TransactionInstruction({
      keys,
      programId: swapProgramId,
      data,
    });
  }

  /**
   * Deposit tokens into the pool
   * @param userAccountA User account for token A
   * @param userAccountB User account for token B
   * @param poolAccount User account for pool token
   * @param userTransferAuthority Account delegated to transfer user's tokens
   * @param poolTokenAmount Amount of pool tokens to mint
   * @param maximumTokenA The maximum amount of token A to deposit
   * @param maximumTokenB The maximum amount of token B to deposit
   */
  async depositAllTokenTypes(
    userAccountA: PublicKey,
    userAccountB: PublicKey,
    poolAccount: PublicKey,
    userTransferAuthority: Account,
    poolTokenAmount: number | Numberu64,
    maximumTokenA: number | Numberu64,
    maximumTokenB: number | Numberu64,
  ): Promise<TransactionSignature> {
    return await sendAndConfirmTransaction(
      'depositAllTokenTypes',
      this.connection,
      new Transaction().add(
        TokenSwap.depositAllTokenTypesInstruction(
          this.tokenSwap,
          this.authority,
          userTransferAuthority.publicKey,
          userAccountA,
          userAccountB,
          this.tokenAccountA,
          this.tokenAccountB,
          this.poolToken,
          poolAccount,
          this.swapProgramId,
          this.tokenProgramId,
          poolTokenAmount,
          maximumTokenA,
          maximumTokenB,
        ),
      ),
      this.payer,
      userTransferAuthority,
    );
  }

  static depositAllTokenTypesInstruction(
    tokenSwap: PublicKey,
    authority: PublicKey,
    userTransferAuthority: PublicKey,
    sourceA: PublicKey,
    sourceB: PublicKey,
    intoA: PublicKey,
    intoB: PublicKey,
    poolToken: PublicKey,
    poolAccount: PublicKey,
    swapProgramId: PublicKey,
    tokenProgramId: PublicKey,
    poolTokenAmount: number | Numberu64,
    maximumTokenA: number | Numberu64,
    maximumTokenB: number | Numberu64,
  ): TransactionInstruction {
    const dataLayout = BufferLayout.struct([
      BufferLayout.u8('instruction'),
      Layout.uint64('poolTokenAmount'),
      Layout.uint64('maximumTokenA'),
      Layout.uint64('maximumTokenB'),
    ]);

    const data = Buffer.alloc(dataLayout.span);
    dataLayout.encode(
      {
        instruction: 2, // Deposit instruction
        poolTokenAmount: new Numberu64(poolTokenAmount).toBuffer(),
        maximumTokenA: new Numberu64(maximumTokenA).toBuffer(),
        maximumTokenB: new Numberu64(maximumTokenB).toBuffer(),
      },
      data,
    );

    const keys = [
      {pubkey: tokenSwap, isSigner: false, isWritable: false},
      {pubkey: authority, isSigner: false, isWritable: false},
      {pubkey: userTransferAuthority, isSigner: true, isWritable: false},
      {pubkey: sourceA, isSigner: false, isWritable: true},
      {pubkey: sourceB, isSigner: false, isWritable: true},
      {pubkey: intoA, isSigner: false, isWritable: true},
      {pubkey: intoB, isSigner: false, isWritable: true},
      {pubkey: poolToken, isSigner: false, isWritable: true},
      {pubkey: poolAccount, isSigner: false, isWritable: true},
      {pubkey: tokenProgramId, isSigner: false, isWritable: false},
    ];
    return new TransactionInstruction({
      keys,
      programId: swapProgramId,
      data,
    });
  }

  /**
   * Withdraw tokens from the pool
   *
   * @param userAccountA User account for token A
   * @param userAccountB User account for token B
   * @param poolAccount User account for pool token
   * @param userTransferAuthority Account delegated to transfer user's tokens
   * @param poolTokenAmount Amount of pool tokens to burn
   * @param minimumTokenA The minimum amount of token A to withdraw
   * @param minimumTokenB The minimum amount of token B to withdraw
   */
  async withdrawAllTokenTypes(
    userAccountA: PublicKey,
    userAccountB: PublicKey,
    poolAccount: PublicKey,
    userTransferAuthority: Account,
    poolTokenAmount: number | Numberu64,
    minimumTokenA: number | Numberu64,
    minimumTokenB: number | Numberu64,
  ): Promise<TransactionSignature> {
    return await sendAndConfirmTransaction(
      'withdraw',
      this.connection,
      new Transaction().add(
        TokenSwap.withdrawAllTokenTypesInstruction(
          this.tokenSwap,
          this.authority,
          userTransferAuthority.publicKey,
          this.poolToken,
          this.feeAccount,
          poolAccount,
          this.tokenAccountA,
          this.tokenAccountB,
          userAccountA,
          userAccountB,
          this.swapProgramId,
          this.tokenProgramId,
          poolTokenAmount,
          minimumTokenA,
          minimumTokenB,
        ),
      ),
      this.payer,
      userTransferAuthority,
    );
  }

  static withdrawAllTokenTypesInstruction(
    tokenSwap: PublicKey,
    authority: PublicKey,
    userTransferAuthority: PublicKey,
    poolMint: PublicKey,
    feeAccount: PublicKey,
    sourcePoolAccount: PublicKey,
    fromA: PublicKey,
    fromB: PublicKey,
    userAccountA: PublicKey,
    userAccountB: PublicKey,
    swapProgramId: PublicKey,
    tokenProgramId: PublicKey,
    poolTokenAmount: number | Numberu64,
    minimumTokenA: number | Numberu64,
    minimumTokenB: number | Numberu64,
  ): TransactionInstruction {
    const dataLayout = BufferLayout.struct([
      BufferLayout.u8('instruction'),
      Layout.uint64('poolTokenAmount'),
      Layout.uint64('minimumTokenA'),
      Layout.uint64('minimumTokenB'),
    ]);

    const data = Buffer.alloc(dataLayout.span);
    dataLayout.encode(
      {
        instruction: 3, // Withdraw instruction
        poolTokenAmount: new Numberu64(poolTokenAmount).toBuffer(),
        minimumTokenA: new Numberu64(minimumTokenA).toBuffer(),
        minimumTokenB: new Numberu64(minimumTokenB).toBuffer(),
      },
      data,
    );

    const keys = [
      {pubkey: tokenSwap, isSigner: false, isWritable: false},
      {pubkey: authority, isSigner: false, isWritable: false},
      {pubkey: userTransferAuthority, isSigner: true, isWritable: false},
      {pubkey: poolMint, isSigner: false, isWritable: true},
      {pubkey: sourcePoolAccount, isSigner: false, isWritable: true},
      {pubkey: fromA, isSigner: false, isWritable: true},
      {pubkey: fromB, isSigner: false, isWritable: true},
      {pubkey: userAccountA, isSigner: false, isWritable: true},
      {pubkey: userAccountB, isSigner: false, isWritable: true},
      {pubkey: feeAccount, isSigner: false, isWritable: true},
      {pubkey: tokenProgramId, isSigner: false, isWritable: false},
    ];
    return new TransactionInstruction({
      keys,
      programId: swapProgramId,
      data,
    });
  }

  /**
   * Deposit one side of tokens into the pool
   * @param userAccount User account to deposit token A or B
   * @param poolAccount User account to receive pool tokens
   * @param userTransferAuthority Account delegated to transfer user's tokens
   * @param sourceTokenAmount The amount of token A or B to deposit
   * @param minimumPoolTokenAmount Minimum amount of pool tokens to mint
   */
  async depositSingleTokenTypeExactAmountIn(
    userAccount: PublicKey,
    poolAccount: PublicKey,
    userTransferAuthority: Account,
    sourceTokenAmount: number | Numberu64,
    minimumPoolTokenAmount: number | Numberu64,
  ): Promise<TransactionSignature> {
    return await sendAndConfirmTransaction(
      'depositSingleTokenTypeExactAmountIn',
      this.connection,
      new Transaction().add(
        TokenSwap.depositSingleTokenTypeExactAmountInInstruction(
          this.tokenSwap,
          this.authority,
          userTransferAuthority.publicKey,
          userAccount,
          this.tokenAccountA,
          this.tokenAccountB,
          this.poolToken,
          poolAccount,
          this.swapProgramId,
          this.tokenProgramId,
          sourceTokenAmount,
          minimumPoolTokenAmount,
        ),
      ),
      this.payer,
      userTransferAuthority,
    );
  }

  static depositSingleTokenTypeExactAmountInInstruction(
    tokenSwap: PublicKey,
    authority: PublicKey,
    userTransferAuthority: PublicKey,
    source: PublicKey,
    intoA: PublicKey,
    intoB: PublicKey,
    poolToken: PublicKey,
    poolAccount: PublicKey,
    swapProgramId: PublicKey,
    tokenProgramId: PublicKey,
    sourceTokenAmount: number | Numberu64,
    minimumPoolTokenAmount: number | Numberu64,
  ): TransactionInstruction {
    const dataLayout = BufferLayout.struct([
      BufferLayout.u8('instruction'),
      Layout.uint64('sourceTokenAmount'),
      Layout.uint64('minimumPoolTokenAmount'),
    ]);

    const data = Buffer.alloc(dataLayout.span);
    dataLayout.encode(
      {
        instruction: 4, // depositSingleTokenTypeExactAmountIn instruction
        sourceTokenAmount: new Numberu64(sourceTokenAmount).toBuffer(),
        minimumPoolTokenAmount: new Numberu64(
          minimumPoolTokenAmount,
        ).toBuffer(),
      },
      data,
    );

    const keys = [
      {pubkey: tokenSwap, isSigner: false, isWritable: false},
      {pubkey: authority, isSigner: false, isWritable: false},
      {pubkey: userTransferAuthority, isSigner: true, isWritable: false},
      {pubkey: source, isSigner: false, isWritable: true},
      {pubkey: intoA, isSigner: false, isWritable: true},
      {pubkey: intoB, isSigner: false, isWritable: true},
      {pubkey: poolToken, isSigner: false, isWritable: true},
      {pubkey: poolAccount, isSigner: false, isWritable: true},
      {pubkey: tokenProgramId, isSigner: false, isWritable: false},
    ];
    return new TransactionInstruction({
      keys,
      programId: swapProgramId,
      data,
    });
  }

  /**
   * Withdraw tokens from the pool
   *
   * @param userAccount User account to receive token A or B
   * @param poolAccount User account to burn pool token
   * @param userTransferAuthority Account delegated to transfer user's tokens
   * @param destinationTokenAmount The amount of token A or B to withdraw
   * @param maximumPoolTokenAmount Maximum amount of pool tokens to burn
   */
  async withdrawSingleTokenTypeExactAmountOut(
    userAccount: PublicKey,
    poolAccount: PublicKey,
    userTransferAuthority: Account,
    destinationTokenAmount: number | Numberu64,
    maximumPoolTokenAmount: number | Numberu64,
  ): Promise<TransactionSignature> {
    return await sendAndConfirmTransaction(
      'withdrawSingleTokenTypeExactAmountOut',
      this.connection,
      new Transaction().add(
        TokenSwap.withdrawSingleTokenTypeExactAmountOutInstruction(
          this.tokenSwap,
          this.authority,
          userTransferAuthority.publicKey,
          this.poolToken,
          this.feeAccount,
          poolAccount,
          this.tokenAccountA,
          this.tokenAccountB,
          userAccount,
          this.swapProgramId,
          this.tokenProgramId,
          destinationTokenAmount,
          maximumPoolTokenAmount,
        ),
      ),
      this.payer,
      userTransferAuthority,
    );
  }

  static withdrawSingleTokenTypeExactAmountOutInstruction(
    tokenSwap: PublicKey,
    authority: PublicKey,
    userTransferAuthority: PublicKey,
    poolMint: PublicKey,
    feeAccount: PublicKey,
    sourcePoolAccount: PublicKey,
    fromA: PublicKey,
    fromB: PublicKey,
    userAccount: PublicKey,
    swapProgramId: PublicKey,
    tokenProgramId: PublicKey,
    destinationTokenAmount: number | Numberu64,
    maximumPoolTokenAmount: number | Numberu64,
  ): TransactionInstruction {
    const dataLayout = BufferLayout.struct([
      BufferLayout.u8('instruction'),
      Layout.uint64('destinationTokenAmount'),
      Layout.uint64('maximumPoolTokenAmount'),
    ]);

    const data = Buffer.alloc(dataLayout.span);
    dataLayout.encode(
      {
        instruction: 5, // withdrawSingleTokenTypeExactAmountOut instruction
        destinationTokenAmount: new Numberu64(
          destinationTokenAmount,
        ).toBuffer(),
        maximumPoolTokenAmount: new Numberu64(
          maximumPoolTokenAmount,
        ).toBuffer(),
      },
      data,
    );

    const keys = [
      {pubkey: tokenSwap, isSigner: false, isWritable: false},
      {pubkey: authority, isSigner: false, isWritable: false},
      {pubkey: userTransferAuthority, isSigner: true, isWritable: false},
      {pubkey: poolMint, isSigner: false, isWritable: true},
      {pubkey: sourcePoolAccount, isSigner: false, isWritable: true},
      {pubkey: fromA, isSigner: false, isWritable: true},
      {pubkey: fromB, isSigner: false, isWritable: true},
      {pubkey: userAccount, isSigner: false, isWritable: true},
      {pubkey: feeAccount, isSigner: false, isWritable: true},
      {pubkey: tokenProgramId, isSigner: false, isWritable: false},
    ];
    return new TransactionInstruction({
      keys,
      programId: swapProgramId,
      data,
    });
  }
}
